﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Linq.Expressions;
using CoreLibrary;
using CoreLibrary.Repo;
using Entities.DataBase.Obj;
using Entities.DataBase.Persistent;
using Entities.Enumeration;

namespace Entities.DataBase.Impl {

    public class PackageRepository : BaseRepository<Package>, IPackageRepository { 

        private static readonly Func<DataContext, string, Package>
          LinqGetPackage = CompiledQuery.Compile((DataContext context, string id)
             => context.GetTable<Package>().SingleOrDefault(p => p.Id.Equals(id)));

        private static readonly Func<DataContext, string, IQueryable<PackageInfo>>
          LinqGetPackageByKeywords = CompiledQuery.Compile((DataContext context, string keywords)
              => context.GetTable<Package>().Where(p => keywords.Equals("") ||
                                                   p.PhoneNumber.Contains(keywords) || 
                                                   p.Recipient.Contains(keywords)  || 
                                                   p.ShippingAddress.Contains(keywords) || 
                                                   p.TrackingId.Equals(keywords))
                                            .OrderByDescending(p => p.DateCreated)
                                            .Select(p => new PackageInfo {
                                                Id = p.Id,
                                                CourierId = p.CourierId,
                                                DateCreated = p.DateCreated,
                                                DateDelivered = p.DateDelivered,
                                                DateUpdated = p.DateUpdated,
                                                PhoneNumber = p.PhoneNumber,
                                                Recipient = p.Recipient,
                                                Reference = p.Reference,
                                                Status = p.Status,
                                                Description = p.Description,
                                                ShippingAddress = p.ShippingAddress,
                                                TrackingId = p.TrackingId,
                                                Type = p.Type
                                            })
        );

        private static readonly Func<DataContext, string, IQueryable<PackageInfo>>
          LinqGetPackageByReference = CompiledQuery.Compile((DataContext context, string reference)
              => context.GetTable<Package>().Where(p => reference.Equals("") || p.Reference.Equals(reference))
              .OrderByDescending(p => p.DateCreated)
                                            .Select(p => new PackageInfo {
                                                Id = p.Id,
                                                CourierId = p.CourierId,
                                                DateCreated = p.DateCreated,
                                                DateDelivered = p.DateDelivered,
                                                DateUpdated = p.DateUpdated,
                                                PhoneNumber = p.PhoneNumber,
                                                Recipient = p.Recipient,
                                                Reference = p.Reference,
                                                Status = p.Status,
                                                Description = p.Description,
                                                ShippingAddress = p.ShippingAddress,
                                                TrackingId = p.TrackingId,
                                                Type = p.Type
                                             })
        );

        public override string TableName {
            get { return "D_PACKAGE"; }
        }

        public void Add(Package package) {
            Execute((context, ts) => {
                context.GetTable<Package>().InsertOnSubmit(package);
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void Remove(string packageId) {
            Execute((context, ts) => {
                var package = LinqGetPackage(context,packageId);
                context.GetTable<Package>().DeleteOnSubmit(package);
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void RemoveByReference(string reference) {
            Execute((context, ts) => {
                var packages = context.GetTable<Package>().Where(p => reference.Equals(p.Reference)).ToList();
                if (!CollectionUtil.IsEmpty(packages)) {
                     context.GetTable<Package>().DeleteAllOnSubmit(packages);
                }

                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void Update(string packageId, PackageUpdator updator) {
            Execute((context, ts) => {
                var package = LinqGetPackage(context, packageId);
                updator.Update(package);
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void UpdateStatus(string packageId, int status) {
            Execute((context, ts) => {
                var package = LinqGetPackage(context, packageId);
                if (status != package.Status) {
                    package.Status = status;
                    package.DateUpdated = DateTime.UtcNow;
                    if (status == PackageStatus.DELIVERED) {
                        package.DateDelivered = DateTime.UtcNow;
                    }
                }
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void UpdateData(string packageId, string data) {
            Execute((context, ts) => {
                var package = LinqGetPackage(context, packageId);
                if (!string.IsNullOrEmpty(data)) {
                    package.Data = Util.GetString(data);
                    package.DateUpdated = DateTime.UtcNow;
                }
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public Package Get(string packageId) {
            return Execute(context => LinqGetPackage(context,packageId));
        }

        public List<PackageInfo> ListAll() {
            return Execute(context => LinqGetPackageByKeywords(context,"").ToList());
        }

        public List<PackageInfo> ListByReference(string reference) {
            return Execute(context => LinqGetPackageByReference(context,Util.GetString(reference)).ToList());
        }

        public List<PackageInfo> ListByKeywords(string keywords) {
            return Execute(context => LinqGetPackageByKeywords(context, Util.GetString(keywords)).ToList());
        }

        public List<PackageInfo> SearchPackages(int? courierId, int? type, string customerName, string trackingId) {
            return Execute(context => {
                var expression = PredicateUtil.And(CreateCriteria(courierId, type, customerName, trackingId));
                return LinqGetPackageByKeywords(context, string.Empty)
                        .Where(expression)
                        .ToList();
            });
        }

        protected static List<Expression<Func<PackageInfo, bool>>> CreateCriteria(int? courierId, int? type, string customerName, string trackingId) {
            var criterias = new List<Expression<Func<PackageInfo, bool>>>();
            Expression<Func<PackageInfo, bool>> func1 = info => courierId == null || info.CourierId == courierId;
            Expression<Func<PackageInfo, bool>> func2 = info => type == null || info.Type == type;
            Expression<Func<PackageInfo, bool>> func3 = info => customerName.Equals("") || info.Recipient.Contains(customerName);
            Expression<Func<PackageInfo, bool>> func4 = info => trackingId.Equals("") || info.TrackingId.Contains(trackingId);
            criterias.Add(func1);
            criterias.Add(func2);
            criterias.Add(func3);
            criterias.Add(func4);
            return criterias;
        }

    }
}